// 所有权：rust核心特性所有权
// 所有程序在运行的时都会管理他们使用计算机内存的方式
// 有些語言有垃圾回收机制在程序运行时，他们会不断的寻找不在使用的内存
// 在其他语言中程序员必须显示的分配和释放内存
// rust使用了第三种方式
// 内存是通过一个所有权系统管理的，其中包含一组编译器在编译时检查的规则。
// 当程序运行时，所有权特性不会减慢程序的运行速度

// 堆内存（heap）和栈内存（stack）
// stack后进先出 添加数据叫压入栈，移除数据叫弹出栈
// 所有存储在stack上的数据必须拥有已知的固定大小
// 编译时大小未知的数据或运行时大小可能发生变化的数据必须存放在heap上
// heap内存组织性差一些
// 当把数据放入heap时，你会请求一定数量的空间
// 操作系统在heap里找到一块足够大的空间，把他标记为在用并返回一个指针，也就是这个空间的地址
// 这个过程叫做在heap上进行分配，有时仅仅成为“分配”
// stack vs heap
// 把值压到stack上不叫做“分配”
// 因为指针是已知固定大小，所以可以把指针放在stack上，但是如果要拿到实际数据必须用指针定位
// 把数据压到stack上要比heap分配来的快的多
// 因为操作系统不需要寻找用来存储新数据的空间，那个位置永远都在stack的顶端
// 在heap上分配空间需要做更多的工作
// 操作系统首先需要找到一个足够大的空间来存放数据，然后要做好记录方便下次分配
// 访问heap中的数据要比stack中的数据来的慢的多因为需要通过指针才能找到heap中的数据
// 现在的处理器来说，由于缓存的缘故如果指令在内存中跳转的次数越少，那么速度越快
// 如果数据存放的距离比较近那么处理器的处理速度就会更快些如stack
// 如果数据存放之间的距离比较远那么处理速度就会慢一些如heap，在heap上分配大量空间也是需要时间
// 函数调用
// 当你的代码调用函数时，值被传入到函数（也包括heap指针），函数本地的变量被压到stack中，但函数结束后这些值被冲stack弹出
// 所有权解决了什么问题：
// 1.跟踪代码的哪些部分正在使用heap哪些数据
// 2.最小化heap上的重复数据量
// 3.清理heap上未使用的数据以避免空间不足

// 所有权规则
// 1.每个值都有一个变量，变量是该值的所有者
// 2.每个值同时只能有一个所有者
// 3.当所有者超出作用域scope时，该值将被删除
// 变量作用域scope就是程序中一个项目的有效范围

// string类型
// 比其他基础标量数据类型更加复杂（一般存在heap上）
// 字符串字面值：程序里手写的那些字符串值。他们是不可变的
// rust还有第二种字符串类型string在heap上分配。能够存储在编译时未知数量的文本。
// 可以使用form函数从字符串字面值创建出String类型
// let s = String::from("hello");
// "::"表示from是String类型下的函数
// 这类字符串是可以被修改的
// String类型的值可以被修改，而字符串字面值却不能修改，因为他们的处理内存方式不同
// 字符串字面值，在编译时就知道他的内容了，其文本内容直接被硬编码到最终的可执行文件里
// 速度快，高效。是因为其不可变性。
// String类型，为了支持可变性，需要在heap上分配内存来保存编译时为止的文本内容
// 操作系统必须在运行时来请求内存
// 这步通过调用String::from来实现
// 这步，在有gc的语言中gc会跟踪并清理不在使用的内存
// 没有gc就需要我们去识别内存何时不在使用，并调用代码将它返回
// rust采用对某个值来说当拥有他的变量走出作用范围时，内存会立即自动交还给操作系统（drop函数）
// 变量和数据的交互方式：移动（move）
// 多个变量可以与同一个数据使用一种独特的方式来交互
// let x = 5; let y = x;整数是已知且固定大小的简单的值，这两个5被压到了stack中。
// let s1 = String::from("hello");let s2 = s1;
// 上述的情况中栈内存中会存放一个指针（prt）长度（len）容量（capacity），堆内存中会存放具体的数据
// 当s1赋值给s2，String的数据被复制了一份，其中栈内存被复制了一份，但是堆内存中没有复制
// 当变量离开作用域的时候会调用drop函数释放heap的内存
// 当s1和s2离开作用域时，他们会尝试释放相同的内存这会导致：二次释放（double free）bug
// rust为了保证内存安全：没有复制heap内存，rust让s1失效（s1离开作用域不需要释放任何东西，上诉例子中放s1赋值给s2时s1失效后面不能在用s1）这个操作就叫移动（move）
// rust不会创建数据的深拷贝其中s1和s2的场景可以理解成浅拷贝
// 所有与函数
// 在语义上将值传递给函数和把值赋给变量是类似的，将值传递给函数将发生移动或者复制
// 函数在返回值的过程中同样会发生所有权的转移
// 一个变量的所有权总是遵循同样的模式
// 把一个值赋值给其他变量时会发生移动
// 当一个包含heap数据的变量离开作用域时，他的值就会被drop函数清理，除非数据的所有权移动到另一个变量上
// 如何让函数使用某个值但是不获得所有权？见如下例子example1
// rust有更优雅的方案引用和借用（就用浅拷贝理解吧）
// 参数的类型是&String而不是String
// &符号就表示引用：允许你引用某些值而不取得其所有权
// 把引用作为函数参数就是借用
// 可变引用有一个重要的限制：在特定作用域内，对某一块数据，只能有一个可变的引用，这样的好处是在编译时避免数据竞争
// 会发生数据竞争的三种情况
// 1.两个或者多个指针同同时访问同一个数据
// 2.至少有一个指针用于写入数据
// 3.没有使用任何机制来同步对数据的访问
// 可以创建新的作用域，来允许非同时的创建多个可变引用
// 限制
// 不可以同时拥有一个可变引用和不可变引用，但是多个不变的引用是可以的
// 悬空引用（悬空指针）
// 一个指针引用了内存中的某个地址，而这块内存可能已经释放并分配给其他人使用
// 在rust里，编译器可保证引用永远都不是悬空引用
// 如果你引用了某些数据，编译器将保证在引用离开作用域之前数据不会离开作用域

// 切片（不持有所有权的数据类型slice切片）是指向字符串中一部分内容的引用
// 字符串切片的范围索引必须发生在有效的UTF-8字符边界内
// 如果尝试从一个多字节的字符中创建字符串切片，程序会报错并退出
// 字符串字面值被直接存储在二进制程序中。
// let s = "hello World!"
// 变量s的类型是&str，它是一个指向二进制程序特定位置的切片
// &str是不可变引用，所以字符串字面值也是不可变的
// 字符串切片作为参数传递
// fn test1(s: &String) -> &str
// fn test2(s: &str) -> &str
// 有经验的rust开发者会采用&str作为参数类型，因为这样就可以同时接收String和&str类型的参数了
// 使用字符串切片，直接调用该函数
// 使用String，可以创建一个完整的String切片来调用该函数
// 定义函数时使用字符串切片来代替字符串引用会使我们的API更加通用，且不会损失任何功能

fn main() {
    let mut s = String::from("hello");
    s.push_str(", world");
    let s1 = String::from("hello");
    let s2 = s1.clone(); // 深拷贝消耗性能 这样s1和s2都能生效
    println!("{}, {}", s2, s1);
    // let s2 = s1;
    // println!("{}", s1); // s1赋值给s2时s1失效后面不能在用s1

    // example1
    let s3 = String::from("test");
    let (s4, len) = get_length(s3);
    println!("{},{}", len, s4);
    // 引用借用
    let mut s5 = String::from("hello");
    let len1 = get_length1(&mut s5);
    println!("{},{}", s5, len1);

    // 数据竞争
    let mut s7 = "hello";
    // 以下会报错因为同一个作用域多次mut
    // let s8 = &mut s7;
    // let s9 = &mut s7;
    // 以下在不同作用域不会报错
    {
        let s8 = &mut s7;
        println!("{}", s8);
    }
    let s9 = &mut s7;
    println!("{}", s9);

    // 悬空指针
    // let r = dangle();

    // 切片
    let slice_string = String::from("world hello");
    let world_index = first_string(&slice_string);
    println!("{}", world_index);
    let world_index1 = first_string1(&slice_string);
    println!("{}", world_index1);

    let silce_s = String::from("hello world");
    let silce_s1 = &silce_s[0..5]; // [..5];
    let silce_s2 = &silce_s[6..11]; // [6..] || [6..slice_s.len()]
    let whole = &silce_s[..]; // [0..slice_s.len()]
    println!("{},{},{}", silce_s1, silce_s2, whole);

    // 其他切片类型
    let arr = [1, 2, 4, 5, 7];
    let arr1 = &arr[0..3];
    println!("{}, {}", arr[1], arr1[0]);
}

// 悬空指针编译时就会报错杜绝bug
// fn dangle() -> &String {
//     let s = String::from("hello");
//     return &s;
// }

fn get_length(string: String) -> (String, usize) {
    let len = string.len();
    return (string, len)
}

fn get_length1(string: &mut String) -> usize {
    string.push_str("2213"); // 因为是借用所以不能修改,要一路加mut才能修改
    return string.len();
}

fn first_string (s: &String) -> usize {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }
    return s.len();
}

// 使用切片改写上述方法

fn first_string1 (s: &String) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    return &s[..];
}